`timescale 1ns/1ps
`default_nettype none

/* NOTE:
* - 本模负责将已处理好的图像数据存入SDRAM
* - 使用4个BRAM，每个BRAM存取8个连续像素中的2个像素的交织结果
* - 先写入数据到BRAM，再发起I_write_req
* - 按照BRAM地址最高位做pingpong
* - 每次最多写入256像素
*/

module pixel_writer (
    // system signal
    input  wire         I_sclk,  // 125M
    input  wire         I_sdram_clk, // 150M
    input  wire         I_rst_n,
    // config
    input  wire [1:0]   I_cfg_scan_mode,
    input  wire [1:0]   I_cfg_box_dir,      // 箱体方向
    input  wire [1:0]   I_cfg_col_loop,     // 列模式读取循环次数(数据组数/8,向上取整), 0表示4
    // write request
    input  wire         I_write_req,
    output wire         O_write_busy,
    input  wire [20:0]  I_write_addr,
    input  wire [5:0]   I_write_len, // 32bitx16数量(8 pixel)
    input  wire         I_write_valid,
    input  wire [31:0]  I_write_data,
    // sdram mux
    output wire         O_mux_req,
    input  wire         I_mux_ack,
    input  wire         I_mux_irq, // 中断，表示有更高优先级的模块正在请求SDRAM控制权
    output wire         O_mux_cs_n,
    output wire         O_mux_ras_n,
    output wire         O_mux_cas_n,
    output wire         O_mux_we_n,
    output wire [1:0]   O_mux_ba,
    output wire [10:0]  O_mux_addr,
    output wire [31:0]  O_mux_dq_out,
    input  wire [31:0]  I_mux_dq_in,
    output wire         O_mux_dq_oe,
    output wire [3:0]   O_mux_dqm
);
//------------------------Parameter----------------------
`include "sdram_common.vh" 

// fsm
localparam [2:0]
    IDLE  = 0,
    ACT   = 1,
    NOP0  = 2,
    NOP1  = 3,
    WRITE = 4,
    WAIT  = 5,
    LOOP  = 6;

// box direction
localparam [1:0]
    LANDSCAPE = 0, // 横向
    PORTRAIT0 = 1, // 纵向，第一个端口在左侧
    PORTRAIT1 = 2; // 纵向，第一个端口在右侧

// wait count
localparam
    WAIT_TIME = (1 << SD_MR_BL) + DELAY_RP - 1;

//------------------------Local signal-------------------
// write request
reg         buf_sel; // pingpong sel
reg  [3:0]  ram_sel;
reg  [6:0]  ram_waddr;
reg  [1:0]  write_req_sr;
reg  [2:0]  busy_sr;
reg         pending;
reg         task0_buf;
reg  [20:0] task0_addr;
reg  [5:0]  task0_len;
reg         task1_buf;
reg  [20:0] task1_addr;
reg  [5:0]  task1_len;

// ram
wire        ram0_wren;
wire [7:0]  ram0_wraddr;
wire [31:0] ram0_data;
wire        ram0_rden;
wire [9:0]  ram0_rdaddr;
wire [7:0]  ram0_q;
wire        ram1_wren;
wire [7:0]  ram1_wraddr;
wire [31:0] ram1_data;
wire        ram1_rden;
wire [9:0]  ram1_rdaddr;
wire [7:0]  ram1_q;
wire        ram2_wren;
wire [7:0]  ram2_wraddr;
wire [31:0] ram2_data;
wire        ram2_rden;
wire [9:0]  ram2_rdaddr;
wire [7:0]  ram2_q;
wire        ram3_wren;
wire [7:0]  ram3_wraddr;
wire [31:0] ram3_data;
wire        ram3_rden;
wire [9:0]  ram3_rdaddr;
wire [7:0]  ram3_q;

// fsm
reg  [2:0]  state;
reg  [2:0]  next;
reg         first_act; // 第一次ACT
reg  [7:0]  total_len;
reg  [2:0]  wait_cnt;

// sdram mux
reg  [1:0]  sdram_req_sr;
wire        sdram_busy;
reg         mux_req;
reg  [3:0]  mux_cmd;
reg  [1:0]  mux_ba;
reg  [1:0]  bit_index; // bit_sel的高2bit
reg  [14:0] sdram_addr;
reg  [10:0] mux_addr;
reg  [31:0] mux_dq_out;
reg         mux_dq_oe;
reg         write_sr;
reg         ram_rden;
reg  [9:0]  ram_rdaddr;
reg         ram_q_valid;
reg  [2:0]  group_num;

//------------------------Instantiation------------------
// sdpram_256x32_1024x8
sdpram_256x32_1024x8 ram0 (/*{{{*/
    .data      ( ram0_data ),
    .rdaddress ( ram0_rdaddr ),
    .rdclock   ( I_sdram_clk ),
    .rden      ( ram0_rden ),
    .wraddress ( ram0_wraddr ),
    .wrclock   ( I_sclk ),
    .wren      ( ram0_wren ),
    .q         ( ram0_q )
);/*}}}*/
// sdpram_lpm #(
    // .A_ADDRESS_WIDTH    ( 8     ),
    // .A_DATA_WIDTH       ( 32    ),
    // .B_ADDRESS_WIDTH    ( 10    ),
    // .B_DATA_WIDTH       ( 8     ) 
// )
// ram0(

// .clka   ( I_sclk ),
// .wea    ( ram0_wren ),
// .addra  ( ram0_wraddr ),
// .dina   ( ram0_data ),

// .clkb   ( I_sdram_clk ),
// .reb    ( ram0_rden ),
// .addrb  ( ram0_rdaddr ),
// .doutb  ( ram0_q )
// );

// sdpram_256x32_1024x8
sdpram_256x32_1024x8 ram1 (/*{{{*/
    .data      ( ram1_data ),
    .rdaddress ( ram1_rdaddr ),
    .rdclock   ( I_sdram_clk ),
    .rden      ( ram1_rden ),
    .wraddress ( ram1_wraddr ),
    .wrclock   ( I_sclk ),
    .wren      ( ram1_wren ),
    .q         ( ram1_q )
);/*}}}*/

// sdpram_lpm #(
    // .A_ADDRESS_WIDTH    ( 8     ),
    // .A_DATA_WIDTH       ( 32    ),
    // .B_ADDRESS_WIDTH    ( 10    ),
    // .B_DATA_WIDTH       ( 8     ) 
// )
// ram1(

// .clka   ( I_sclk ),
// .wea    ( ram1_wren ),
// .addra  ( ram1_wraddr ),
// .dina   ( ram1_data ),

// .clkb   ( I_sdram_clk ),
// .reb    ( ram1_rden ),
// .addrb  ( ram1_rdaddr ),
// .doutb  ( ram1_q )
// );

// sdpram_256x32_1024x8
sdpram_256x32_1024x8 ram2 (/*{{{*/
    .data      ( ram2_data ),
    .rdaddress ( ram2_rdaddr ),
    .rdclock   ( I_sdram_clk ),
    .rden      ( ram2_rden ),
    .wraddress ( ram2_wraddr ),
    .wrclock   ( I_sclk ),
    .wren      ( ram2_wren ),
    .q         ( ram2_q )
);/*}}}*/
// sdpram_lpm #(
    // .A_ADDRESS_WIDTH    ( 8     ),
    // .A_DATA_WIDTH       ( 32    ),
    // .B_ADDRESS_WIDTH    ( 10    ),
    // .B_DATA_WIDTH       ( 8     ) 
// )
// ram2(

// .clka   ( I_sclk ),
// .wea    ( ram2_wren ),
// .addra  ( ram2_wraddr ),
// .dina   ( ram2_data ),

// .clkb   ( I_sdram_clk ),
// .reb    ( ram2_rden ),
// .addrb  ( ram2_rdaddr ),
// .doutb  ( ram2_q )
// );

// sdpram_256x32_1024x8
sdpram_256x32_1024x8 ram3 (/*{{{*/
    .data      ( ram3_data ),
    .rdaddress ( ram3_rdaddr ),
    .rdclock   ( I_sdram_clk ),
    .rden      ( ram3_rden ),
    .wraddress ( ram3_wraddr ),
    .wrclock   ( I_sclk ),
    .wren      ( ram3_wren ),
    .q         ( ram3_q )
);/*}}}*/

// sdpram_lpm #(
    // .A_ADDRESS_WIDTH    ( 8     ),
    // .A_DATA_WIDTH       ( 32    ),
    // .B_ADDRESS_WIDTH    ( 10    ),
    // .B_DATA_WIDTH       ( 8     ) 
// )
// ram3(

// .clka   ( I_sclk ),
// .wea    ( ram3_wren ),
// .addra  ( ram3_wraddr ),
// .dina   ( ram3_data ),

// .clkb   ( I_sdram_clk ),
// .reb    ( ram3_rden ),
// .addrb  ( ram3_rdaddr ),
// .doutb  ( ram3_q )
// );

//------------------------Body---------------------------
//{{{+++++++++++++++++++++write request++++++++++++++++++
// NOTE:
// 最多支持两个写入任务
// - 空闲时，收到I_write_req，将任务写入到task0
// - 处理任务时，收到I_write_req，将任务写入到task1，并设置pending标志

assign O_write_busy = pending;

// buf_sel
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        buf_sel <= 1'b0;
    else if (I_write_req & ~pending)
        buf_sel <= ~buf_sel;
end

// ram_sel
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        ram_sel <= 4'b0001;
    else if (I_write_req)
        ram_sel <= 4'b0001;
    else if (I_write_valid && ram_waddr[1:0] == 2'b11)
        ram_sel <= {ram_sel[2:0], ram_sel[3]};
end

// ram_waddr
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        ram_waddr <= 1'b0;
    else if (I_write_req)
        ram_waddr <= 1'b0;
    else if (I_write_valid) begin
        if (ram_sel[3])
            ram_waddr <= ram_waddr + 1'b1;
        else
            ram_waddr[1:0] <= ram_waddr[1:0] + 1'b1;
    end
end

// write_req_sr
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        write_req_sr <= 1'b0;
    else if (I_write_req & ~busy_sr[1])
        write_req_sr <= 2'b11;
    else if (busy_sr[2:1] == 2'b10 && pending)
        write_req_sr <= 2'b11;
    else
        write_req_sr <= write_req_sr << 1;
end

// busy_sr
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        busy_sr <= 1'b0;
    else
        busy_sr <= {busy_sr[1:0], sdram_busy};
end

// pending
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        pending <= 1'b0;
    else if (I_write_req & busy_sr[1])
        pending <= 1'b1;
    else if (busy_sr[2:1] == 2'b10)
        pending <= 1'b0;
end

// task0_*
always @(posedge I_sclk) begin
    if (I_write_req & ~busy_sr[1]) begin
        task0_buf  <= buf_sel;
        task0_addr <= I_write_addr;
        task0_len  <= I_write_len;
    end
    else if (busy_sr[2:1] == 2'b10 && pending) begin
        task0_buf  <= task1_buf;
        task0_addr <= task1_addr;
        task0_len  <= task1_len;
    end
end

// task1_*
always @(posedge I_sclk) begin
    if (I_write_req & busy_sr[1]) begin
        task1_buf  <= buf_sel;
        task1_addr <= I_write_addr;
        task1_len  <= I_write_len;
    end
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++ram++++++++++++++++++++++++++++
assign ram0_wren   = ram_sel[0] & I_write_valid;
assign ram0_wraddr = {buf_sel, ram_waddr};
assign ram0_data   = I_write_data;
assign ram0_rden   = ram_rden;
assign ram0_rdaddr = ram_rdaddr;

assign ram1_wren   = ram_sel[1] & I_write_valid;
assign ram1_wraddr = {buf_sel, ram_waddr};
assign ram1_data   = I_write_data;
assign ram1_rden   = ram_rden;
assign ram1_rdaddr = ram_rdaddr;

assign ram2_wren   = ram_sel[2] & I_write_valid;
assign ram2_wraddr = {buf_sel, ram_waddr};
assign ram2_data   = I_write_data;
assign ram2_rden   = ram_rden;
assign ram2_rdaddr = ram_rdaddr;

assign ram3_wren   = ram_sel[3] & I_write_valid;
assign ram3_wraddr = {buf_sel, ram_waddr};
assign ram3_data   = I_write_data;
assign ram3_rden   = ram_rden;
assign ram3_rdaddr = ram_rdaddr;
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++fsm++++++++++++++++++++++++++++
// state
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        state <= IDLE;
    else
        state <= next;
end

// next
always @(*) begin
    case (state)
        IDLE: begin
            if (sdram_req_sr[1])
                next = ACT;
            else
                next = IDLE;
        end

        ACT: begin
            if (!first_act)
                next = NOP0;
            else if (I_mux_ack)
                next = NOP1;
            else
                next = ACT;
        end

        NOP0: begin
            next = NOP1; 
        end

        NOP1: begin
            next = WRITE;
        end

        WRITE: begin
            if (total_len == 1'b1)
                next = WAIT;
            else if (total_len[1:0] == 1'b1 && I_mux_irq)
                next = WAIT;
            else
                next = ACT;
        end

        WAIT: begin
            if (wait_cnt >= WAIT_TIME)
                next = LOOP;
            else
                next = WAIT;
        end

        LOOP: begin
            if (total_len > 1'b0)
                next = ACT;
            else
                next = IDLE;
        end

        default: begin
            next = IDLE;
        end
    endcase
end

// first_act
always @(posedge I_sdram_clk) begin
    if (state == IDLE || state == LOOP)
        first_act <= 1'b1;
    else if (state == WRITE)
        first_act <= 1'b0;
end

// total_len
always @(posedge I_sdram_clk) begin
    if (state == IDLE && next == ACT)
        total_len <= {task0_len, 2'd0}; // NOTE: 设置false path
    else if (state == WRITE)
        total_len <= total_len - 1'b1;
end

// wait_cnt
always @(posedge I_sdram_clk) begin
    if (state == WRITE)
        wait_cnt <= 1'b0;
    else if (state == WAIT)
        wait_cnt <= wait_cnt + 1'b1;
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++sdram mux++++++++++++++++++++++
assign O_mux_req    = mux_req;
assign O_mux_cs_n   = mux_cmd[3];
assign O_mux_ras_n  = mux_cmd[2];
assign O_mux_cas_n  = mux_cmd[1];
assign O_mux_we_n   = mux_cmd[0];
assign O_mux_ba     = mux_ba;
assign O_mux_addr   = mux_addr;
assign O_mux_dq_out = mux_dq_out;
assign O_mux_dq_oe  = mux_dq_oe;
assign O_mux_dqm    = 1'b0;

assign sdram_busy = (state != IDLE);


// sdram_req_sr
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        sdram_req_sr <= 1'b0;
    else
        sdram_req_sr <= {sdram_req_sr[0], write_req_sr[1]};
end

// mux_req
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        mux_req <= 1'b0;
    else if (state == ACT)
        mux_req <= 1'b1;
    else if (state == LOOP)
        mux_req <= 1'b0;
end

// mux_cmd
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        mux_cmd <= SD_CMD_NOP;
    else if (state == ACT) begin
        if (first_act & I_mux_ack)
            mux_cmd <= SD_CMD_NOP;
        else
            mux_cmd <= SD_CMD_ACT;
    end
    else if (state == WRITE)
        mux_cmd <= SD_CMD_WRIT;
    else
        mux_cmd <= SD_CMD_NOP;
end

// mux_ba
always @(posedge I_sdram_clk) begin
    if (state == IDLE && next == ACT)
        mux_ba <= task0_addr[20:19]; // NOTE: 设置false path
    else if (write_sr)
        mux_ba <= mux_ba + 1'b1;
end

// bit_index
always @(posedge I_sdram_clk) begin
    if (state == IDLE && next == ACT)
        bit_index <= 1'b0;
    else if (state == WRITE)
        bit_index <= bit_index + 1'b1;
end

// sdram_addr
always @(posedge I_sdram_clk) begin
    if (state == IDLE && next == ACT)
        sdram_addr <= task0_addr[14:0]; // NOTE: 设置false path
    else if (state == WRITE && total_len[1:0] == 1'b1) begin
            if(I_cfg_scan_mode==2)
                sdram_addr <= sdram_addr + 6'd16;
            else
                sdram_addr <= sdram_addr + 6'd32;
    end
end

// mux_addr
always @(posedge I_sdram_clk) begin
    if (state == ACT)
        mux_addr <= {task0_addr[18:17], bit_index, sdram_addr[14:8]};
    else if (state == WRITE)
        mux_addr <= {1'b1, 2'd0, sdram_addr[7:0]};
end

// mux_dq_out
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        mux_dq_out <= 1'b0;
    else if (ram_q_valid)
        mux_dq_out <= {ram3_q, ram2_q, ram1_q, ram0_q}/*32'hFFFFFFFF*/;
end

// mux_dq_oe
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        mux_dq_oe <= 1'b0;
    else
        mux_dq_oe <= ram_q_valid;
end

// write_sr
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        write_sr <= 1'b0;
    else
        write_sr <= (state == WRITE);
end

// ram_rden
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        ram_rden <= 1'b0;
    else if (state == ACT && next == NOP1)
        ram_rden <= 1'b1;
    else if (state == NOP0)
        ram_rden <= 1'b1;
    else if (state == WAIT && wait_cnt == 1'd1)
        ram_rden <= 1'b0;
end

// ram_rdaddr
always @(posedge I_sdram_clk) begin
    if (state == IDLE)
        ram_rdaddr <= {task0_buf, 9'd0};
    else if (ram_rden)
        ram_rdaddr <= ram_rdaddr + 1'b1;
end

// ram_q_valid
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        ram_q_valid <= 1'b0;
    else
        ram_q_valid <= ram_rden;
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++


endmodule

`default_nettype wire

// vim:set ts=4 sw=4 et fenc=utf-8 fdm=marker:
